﻿namespace SoftTeach.ViewModel.Helper
{
  using System;
  using System.Collections;
  using System.Collections.Generic;

  /// <summary>
  /// Collections that holds elements in the specified order. The complexity and efficiency
  /// of the algorithm is comparable to the SortedList from .NET collections. In contrast 
  /// to the SortedList SortedCollection accepts redundant elements. If no comparer is 
  /// is specified the list will use the default comparer for given type.
  /// </summary>
  /// <author>consept</author>
  public class SortedCollection<TValue> : IList<TValue>
  {
    // Fields
    private const int DefaultCapacity = 4;

    private static readonly TValue[] EmptyValues;

    private readonly IComparer<TValue> comparer;
    private TValue[] values;
    private int size;
    // for enumeration
    private int version;

    static SortedCollection()
    {
      EmptyValues = new TValue[0];
    }

    // Constructors
    public SortedCollection()
    {
      this.values = EmptyValues;
      this.comparer = Comparer<TValue>.Default;
    }

    public SortedCollection(IComparer<TValue> comparer)
    {
      this.values = EmptyValues;
      this.comparer = comparer;
    }

    // Methods
    private void CheckCapacity(int min)
    {
      // double the capacity
      int num = this.values.Length == 0 ? DefaultCapacity : this.values.Length * 2;
      if (min > num)
      {
        num = min;
      }
      this.Capacity = num;
    }

    public virtual void Insert(int index, TValue value)
    {
      if (value == null)
      {
        throw new ArgumentException("Value can't be null.");
      }
      if (index < 0 || index > this.size)
      {
        throw new ArgumentOutOfRangeException();
      }
      if (this.size == this.values.Length)
      {
        this.CheckCapacity(this.size + 1);
      }
      if (index < this.size)
      {
        Array.Copy(this.values, index, this.values, index + 1, this.size - index);
      }
      this.values[index] = value;
      this.size++;
      this.version++;
    }

    public void Add(TValue value)
    {
      if (value == null)
      {
        throw new ArgumentException("Value can't be null");
      }
      // check where the element should be placed
      int index = Array.BinarySearch<TValue>(this.values, 0, this.size, value, this.comparer);
      if (index < 0)
      {
        // xor
        index = ~index;
      }
      this.Insert(index, value);
    }

    public virtual void Clear()
    {
      this.version++;
      Array.Clear(this.values, 0, this.size);
      this.size = 0;
    }

    public int IndexOf(TValue value)
    {
      if (value == null)
      {
        throw new ArgumentException("Value can't be null.");
      }
      int index = Array.BinarySearch<TValue>(this.values, 0, this.size, value, this.comparer);
      if (index >= 0)
      {
        return index;
      }
      return -1;
    }

    public bool Contains(TValue value)
    {
      return this.IndexOf(value) >= 0;
    }

    public void CopyTo(TValue[] array, int arrayIndex)
    {
      Array.Copy(this.values, 0, array, arrayIndex, this.size);
    }

    public int Count
    {
      get { return this.size; }
    }

    public bool IsReadOnly
    {
      get { return false; }
    }

    public bool Remove(TValue value)
    {
      int index = this.IndexOf(value);
      if (index < 0)
      {
        return false;
      }
      this.RemoveAt(index);
      return true;
    }

    public IEnumerator<TValue> GetEnumerator()
    {
      return new SortedCollectionEnumerator(this);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
      return new SortedCollectionEnumerator(this);
    }

    // Properties
    public int Capacity
    {
      get { return this.values.Length; }
      set
      {
        if (this.values.Length != value)
        {
          if (value < this.size)
          {
            throw new ArgumentException("Too small capacity.");
          }
          if (value > 0)
          {
            TValue[] tempValues = new TValue[value];
            if (this.size > 0)
            {
              // copy only when size is greater than zero
              Array.Copy(this.values, 0, tempValues, 0, this.size);
            }
            this.values = tempValues;
          }
          else
          {
            this.values = EmptyValues;
          }
        }
      }
    }

    public virtual void RemoveAt(int index)
    {
      if (index < 0 || index >= this.size)
      {
        throw new ArgumentOutOfRangeException();
      }
      this.size--;
      this.version++;
      Array.Copy(this.values, index + 1, this.values, index, this.size - index);
      this.values[this.size] = default(TValue);
    }

    public virtual TValue this[int index]
    {
      get
      {
        if (index < 0 || index >= this.size)
        {
          throw new ArgumentOutOfRangeException();
        }
        return this.values[index];
      }
      set
      {
        if (index < 0 || index >= this.size)
        {
          throw new ArgumentOutOfRangeException();
        }
        this.values[index] = value;
        this.version++;
      }
    }

    [Serializable]
    private sealed class SortedCollectionEnumerator : IEnumerator<TValue>, IDisposable, IEnumerator
    {
      // Fields
      [NonSerialized]
      private readonly SortedCollection<TValue> collection;

      private TValue currentValue;
      private int index;
      private int version;

      // Methods
      internal SortedCollectionEnumerator(SortedCollection<TValue> collection)
      {
        this.collection = collection;
        this.version = collection.version;
      }

      public void Dispose()
      {
        this.index = 0;
        this.currentValue = default(TValue);
      }

      public bool MoveNext()
      {
        if (this.version != this.collection.version)
        {
          throw new ArgumentException("Collection was changed while iterating!");
        }
        if (this.index < this.collection.Count)
        {
          this.currentValue = this.collection.values[this.index];
          this.index++;
          return true;
        }
        this.index = this.collection.Count + 1;
        this.currentValue = default(TValue);
        return false;
      }

      void IEnumerator.Reset()
      {
        if (this.version != this.collection.version)
        {
          throw new ArgumentException("Collection was changed while iterating!");
        }
        this.index = 0;
        this.currentValue = default(TValue);
      }

      // Properties
      public TValue Current
      {
        get
        {
          return this.currentValue;
        }
      }

      object IEnumerator.Current
      {
        get
        {
          if ((this.index == 0) || (this.index == this.collection.Count + 1))
          {
            throw new ArgumentException("Enumerator not initilized. Call MoveNext first.");
          }
          return this.currentValue;
        }
      }
    }
  }
}